#include "../common/mmo.h"
#include "../common/showmsg.h"
#include "../common/malloc.h"
#include "../common/timer.h"
#include "../common/socket.h"
#include "../common/strlib.h"
#include "../common/db.h"

#include "map.h"
#include "harmony.h"
#include "pc.h"
#include "clif.h"
#include "battle.h"
#include "log.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

struct s_harmony_map_data {
	int grf_react_tid;
};

struct s_hashblock {
	uint32 id;
	char *name;
	size_t num_expected;
	uint32 *expected;

	struct s_hashblock *next;
};

// --

uint32 hashblock_time;
static DBMap *hashblock_db = NULL;
static FILE *log_fp = NULL;

// --

void hashblock_load(void);
void hashblock_unload(void);

// --

void do_init_harmony(void) {
	ShowNotice("Opening Harmony log file..\n");
	log_fp = fopen("./harmony_log.txt", "a");
	if( !log_fp ) {
		ShowError("Failed to open Harmony log file! Please make sure eA has proper write access to access/create harmony_log.txt.\n");
#ifndef WIN32
		ShowError("To create the file manually, use: touch harmony_log.txt && chmod 0777 harmony_log.txt (in eA directory).\n");
#endif
		exit(EXIT_FAILURE); // I wants attention.
	}

	hashblock_load();
}

void do_final_harmony(void) {
	hashblock_unload();
	if( log_fp )
		fclose(log_fp);
}

void harmony_log(struct map_session_data *sd, const char *format, ...) {
	va_list args;
	va_start(args, format);

	if( sd )
		fprintf(log_fp, "%u\t%u.%u.%u.%u\t%s\t%u\t%u\t%s\t", 
			(uint32)time(NULL), CONVIP(sd->fd > 1 ? session[sd->fd]->client_addr : 0),
			sd->mac_address, sd->status.account_id, sd->status.char_id, sd->status.name);
	else
		fprintf(log_fp, "%u\t-\t-\t-\t-\t-\t", (unsigned int)time(NULL));

	vfprintf(log_fp, format, args);
}

void harm_process_login(struct map_session_data *sd) {
	//CREATE(sd->harmony, struct s_harmony_map_data, 1);
}
void harm_process_logout(struct map_session_data *sd) {
	//aFree(sd->harmony);
}

bool harm_process_login_request(int fd, struct map_session_data *sd) {
	if( RFIFOL(fd, 46) != hashblock_time )
	{
		ShowWarning("Rejecting login attempt from %u.%u.%u.%u - invalid or too old harmonia.bin (%u / %u).\n", CONVIP(session[fd]->client_addr), RFIFOL(fd, 46), hashblock_time);
		WFIFOHEAD(fd,packet_len(0x6a));
		WFIFOW(fd,0) = 0x6a;
		WFIFOB(fd,2) = 3; // Rejected by server
		WFIFOSET(fd,packet_len(0x6a));
		set_eof(fd);
		return false;
	}

	sprintf(sd->mac_address, "%02x:%02x:%02x:%02x:%02x:%02x", RFIFOB(fd, 40), RFIFOB(fd, 41), RFIFOB(fd, 42), RFIFOB(fd, 43), RFIFOB(fd, 44), RFIFOB(fd, 45));
	return true;
}

// --

struct s_hashblock *hashblock_get(int id)
{
	return (struct s_hashblock*)idb_get(hashblock_db, id);
}

void hashblock_unload(void)
{
	DBIterator *dbi = db_iterator(hashblock_db);
	struct s_hashblock *it;
	
	for( it = (struct s_hashblock *)dbi_first(dbi); it; it = dbi_next(dbi) ) {
		aFree(it->name);
		aFree(it->expected);
	}
	dbi_destroy(dbi);
	db_destroy(hashblock_db);
}

void hashblock_load(void) {
	FILE *fp;
	size_t i, m;

	hashblock_db = idb_alloc(DB_OPT_RELEASE_DATA);

	if( (fp = fopen("./harmony/hashblock_server.bin", "rb")) == NULL ) {
		ShowError("can't read harmony/hashblock_server.bin\n");
		exit(EXIT_FAILURE); // i wants attention.
	}

	if( !fread(&hashblock_time, sizeof(uint32), 1, fp) )
		goto __fail;

	m = 0;
	if( !fread(&m, sizeof(uint16), 1, fp) )
		goto __fail;

	for( i = 0; i < m; i++ ) {
		struct s_hashblock *e;
		size_t nl = 0;

		CREATE(e, struct s_hashblock, 1);
		if( !fread(&e->id, sizeof(uint16), 1, fp) || !fread(&nl, sizeof(uint8), 1, fp) )
			goto __fail;

		CREATE(e->name, char, ++nl);
		if( !fread(e->name, sizeof(int8), nl, fp) || !fread(&e->num_expected, sizeof(uint8), 1, fp) )
			goto __fail;
		
		CREATE(e->expected, uint32, nl);
		if( !fread(e->expected, sizeof(uint32), e->num_expected, fp) )
			goto __fail;

		idb_put(hashblock_db, e->id, e);
	}
	i = ftell(fp);
	fseek(fp, 0, SEEK_END);
	if( ftell(fp) != i )
		goto __fail;
	fclose(fp);

	ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", m, "hashblock_server.bin");
	return;

__fail:
	if( fp )
		fclose(fp);
	ShowError("Invalid harmony/hashblock_server.bin!\n");
	exit(EXIT_FAILURE); // i wants attention.
}

void hashblock_reload(void)
{
	hashblock_unload();
	hashblock_load();
}

void hashblock_check(struct map_session_data *sd, int id, uint32 value)
{
	struct s_hashblock *e = hashblock_get(id);
	char output[256];

	size_t i;
	if( !e ) {
		// Srsly..?
		ShowWarning("Invalid GRF check response. Player: %u / %s | %d\n", sd->status.account_id, sd->status.name, id);
		set_eof(sd->fd);
		return;
	}

	if( pc_isGM(sd) >= battle_config.harm_grfhash_gm_bypass_level )
		return;

	for( i = 0; i < e->num_expected; i++ )
	{
		if( e->expected[i] == value )
			return; // OK!
	}

	// SQL Log
	sprintf(output,"%s - %u", e->name, value);
	log_harm_grfblock(sd,output);
	harmony_log(sd, "GrfMismatch\t%s\t%u\n", e->name, value); // Write to harmony_log.txt
	ShowWarning("Player '"CL_WHITE"%s"CL_RESET"' failed '"CL_WHITE"%s"CL_RESET"' GRF check. Sent checksum: %u\n", sd->status.name, e->name, value);

	sprintf(output,"Failed GRF Checksum on file : %s",e->name);
	clif_disp_onlyself(sd, output, strlen(output));
	sprintf(output,"Your Checksum : %u", value);
	clif_disp_onlyself(sd, output, strlen(output));

	if( !sd->state.harmony_block )
	{
		sd->state.harmony_block = 1;
		pc_stop_attack(sd);

		clif_displaymessage(sd->fd,"Please update your client and remove any custom GRF.");
		clif_displaymessage(sd->fd,"Take a screenshot and report at forums if you think this is a valid CheckSum.");
		clif_displaymessage(sd->fd,"You are not allowed to attack.");
	}
}

void harm_parse_GrfHashblockAck(int fd,struct map_session_data *sd) {
	hashblock_check(sd, RFIFOW(fd, 2), RFIFOL(fd, 4));
}

void harm_parse_Notification(int fd,struct map_session_data *sd) {
	if( RFIFOB(fd, 2) == 5 )
		clif_authfail_fd(fd, 3);
	harmony_log(sd, "Notification\t%d", RFIFOB(fd, 4));
	if( log_fp ) {
		uint16 dat_size = RFIFOW(fd, 2) - 5;
		if( dat_size ) {
			size_t i;
			fprintf(log_fp, "\t%d\t", dat_size);
			for( i = 0; i < dat_size; i++ ) {
				fprintf(log_fp, "%.2x", RFIFOB(fd, 5+i));
			}
		}
		fprintf(log_fp, "\n");
	}
	harmony_log(sd, "%d\t%u\t%u\t0x%.8x%.8x\n", RFIFOB(fd, 2), RFIFOL(fd, 3), RFIFOL(fd, 7), RFIFOL(fd, 15), RFIFOL(fd, 11));
}

// --

